"""
摄像头工具模块，提供摄像头控制和图像获取功能。
增强了错误处理和自动恢复能力。
"""
import cv2
import numpy as np
import time
import threading
import os
from typing import Optional, Tuple, Union, List, Dict, Any


class CameraManager:
    """
    摄像头管理类，使用单例模式确保全局只有一个摄像头实例。
    提供摄像头初始化、参数调整、图像获取等功能。
    具有错误自动恢复功能和线程安全操作的摄像头管理类。
    使用单例模式确保全局只有一个摄像头实例。
    """
    _instance = None
    _lock = threading.RLock()  # 可重入锁，用于线程安全操作

    def __new__(cls, *args, **kwargs):
        with cls._lock:
            if cls._instance is None:
                cls._instance = super(CameraManager, cls).__new__(cls)
                cls._instance._initialized = False
            return cls._instance

    def __init__(self, camera_id: int = 0, width: int = 3840, height: int = 2160,
                 auto_recovery: bool = True, recovery_attempts: int = 3,
                 recovery_delay: float = 1.0):
        """
        初始化摄像头管理器

        参数:
            camera_id: 摄像头ID，默认为0（主摄像头）
            width: 图像宽度
            height: 图像高度
            auto_recovery: 是否启用自动恢复功能
            recovery_attempts: 最大恢复尝试次数
            recovery_delay: 恢复尝试间隔时间(秒)
        """
        with self._lock:
            if self._initialized:
                return

            self._camera_id = camera_id
            self._width = width
            self._height = height
            self._cap = None
            self._zoom_factor = 1.0
            self._zoom_center = (0.5, 0.5)  # 缩放中心点(x, y)，范围为0-1

            # 自动恢复参数
            self._auto_recovery = auto_recovery
            self._recovery_attempts = recovery_attempts
            self._recovery_delay = recovery_delay
            self._recovery_count = 0
            self._last_frame_time = 0
            self._frame_timeout = 2.0  # 帧超时时间(秒)
            self._last_error = None

            # 摄像头状态
            self._is_opened = False
            self._last_frame = None
            self._frame_count = 0

            self._initialized = True

    def open(self) -> bool:
        """
        打开摄像头

        返回:
            bool: 是否成功打开摄像头
        """
        with self._lock:
            # 如果摄像头已打开，直接返回
            if self._cap is not None and self._cap.isOpened():
                self._is_opened = True
                return True

            try:
                # 关闭之前的实例（如果有）
                if self._cap is not None:
                    self._cap.release()

                # 重置恢复计数
                self._recovery_count = 0

                # 创建新的摄像头实例
                self._cap = cv2.VideoCapture(self._camera_id)
                if not self._cap.isOpened():
                    self._last_error = f"无法打开摄像头 ID: {self._camera_id}"
                    print(self._last_error)
                    self._is_opened = False
                    return False

                # 设置属性
                self._cap.set(cv2.CAP_PROP_FRAME_WIDTH, self._width)
                self._cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self._height)

                # 测试是否能获取帧
                ret, test_frame = self._cap.read()
                if not ret or test_frame is None or test_frame.size == 0:
                    self._last_error = "摄像头已打开但无法获取图像"
                    print(self._last_error)
                    self._cap.release()
                    self._cap = None
                    self._is_opened = False
                    return False

                # 更新状态
                self._is_opened = True
                self._last_frame_time = time.time()
                print(
                    f"成功打开摄像头 ID: {self._camera_id}, 分辨率: {self._width}x{self._height}")
                return True

            except Exception as e:
                self._last_error = f"打开摄像头时发生异常: {str(e)}"
                print(self._last_error)
                if self._cap is not None:
                    try:
                        self._cap.release()
                    except:
                        pass
                self._cap = None
                self._is_opened = False
                return False

    def close(self) -> None:
        """关闭摄像头并释放资源"""
        with self._lock:
            if self._cap is not None:
                try:
                    self._cap.release()
                except Exception as e:
                    print(f"关闭摄像头时发生异常: {str(e)}")
                finally:
                    self._cap = None
                    self._is_opened = False
                    self._last_frame = None
                    print("摄像头已关闭")

    def is_opened(self) -> bool:
        """
        检查摄像头是否已打开

        返回:
            bool: 摄像头是否已打开
        """
        with self._lock:
            # 检查内部状态和设备状态
            device_opened = self._cap is not None and self._cap.isOpened()

            # 如果状态不一致，更新内部状态
            if self._is_opened != device_opened:
                self._is_opened = device_opened

            return self._is_opened

    def _try_recovery(self) -> bool:
        """
        尝试恢复摄像头连接

        返回:
            bool: 是否成功恢复
        """
        if not self._auto_recovery:
            return False

        if self._recovery_count >= self._recovery_attempts:
            print(f"摄像头恢复尝试次数已达上限({self._recovery_attempts}次)，放弃恢复")
            return False

        self._recovery_count += 1
        print(f"尝试恢复摄像头连接 (第{self._recovery_count}次)...")

        # 关闭现有连接
        if self._cap is not None:
            try:
                self._cap.release()
            except:
                pass
            self._cap = None

        # 延时
        time.sleep(self._recovery_delay)

        # 重新打开
        return self.open()

    def capture_frame(self) -> Optional[np.ndarray]:
        """
        捕获一帧图像，包含自动恢复机制

        返回:
            Optional[np.ndarray]: 图像数组，如果获取失败则返回None
        """
        with self._lock:
            # 检查摄像头状态
            if not self.is_opened():
                if not self.open():
                    # 如果打开失败且启用了自动恢复，尝试恢复
                    if self._auto_recovery and not self._try_recovery():
                        # 如果有缓存的最后一帧，返回它
                        if self._last_frame is not None:
                            print("使用缓存的上一帧图像")
                            return self._last_frame.copy()
                        return None

            try:
                # 捕获图像
                if self._cap is None:
                    self._last_error = "摄像头对象为空"
                    print(self._last_error)
                    return None

                ret, frame = self._cap.read()

                # 更新时间戳
                current_time = time.time()

                # 检查是否成功获取图像
                if not ret or frame is None or frame.size == 0:
                    self._last_error = "读取图像失败"
                    print(self._last_error)

                    # 检查是否需要自动恢复
                    if self._auto_recovery:
                        time_since_last_frame = current_time - self._last_frame_time
                        if time_since_last_frame > self._frame_timeout:
                            print(f"超过{self._frame_timeout}秒未获取到有效图像，尝试恢复...")
                            self._try_recovery()

                    # 如果有缓存的最后一帧，返回它
                    if self._last_frame is not None:
                        print("使用缓存的上一帧图像")
                        return self._last_frame.copy()
                    return None

                # 更新帧计数和时间戳
                self._frame_count += 1
                self._last_frame_time = current_time
                self._recovery_count = 0  # 成功获取图像，重置恢复计数

                # 应用缩放
                if self._zoom_factor != 1.0:
                    try:
                        frame = self._apply_zoom(frame)
                    except Exception as e:
                        print(f"应用缩放时出错: {str(e)}")
                        # 如果缩放失败，返回原始图像

                # 缓存最后一帧
                self._last_frame = frame.copy()

                return frame

            except Exception as e:
                self._last_error = f"捕获图像时发生异常: {str(e)}"
                print(self._last_error)

                # 尝试恢复
                if self._auto_recovery:
                    self._try_recovery()

                # 如果有缓存的最后一帧，返回它
                if self._last_frame is not None:
                    print("使用缓存的上一帧图像")
                    return self._last_frame.copy()
                return None

    def _apply_zoom(self, frame: np.ndarray) -> np.ndarray:
        """
        对图像应用缩放效果

        参数:
            frame: 原始图像

        返回:
            np.ndarray: 缩放后的图像
        """
        if frame is None or frame.size == 0:
            raise ValueError("无效的图像数据")

        h, w = frame.shape[:2]

        # 计算缩放参数
        cx, cy = int(w * self._zoom_center[0]), int(h * self._zoom_center[1])

        # 计算缩放区域
        zoom_width = int(w / self._zoom_factor)
        zoom_height = int(h / self._zoom_factor)

        # 安全检查：确保缩放区域有效
        if zoom_width <= 0 or zoom_height <= 0:
            print(f"警告: 无效的缩放因子 {self._zoom_factor}，使用原始图像")
            return frame

        # 确保缩放区域不超出图像范围
        x1 = max(0, cx - zoom_width // 2)
        y1 = max(0, cy - zoom_height // 2)
        x2 = min(w, x1 + zoom_width)
        y1 = max(0, y1 - (zoom_height - (x2 - x1)) // 2)
        y2 = min(h, y1 + zoom_height)
        x1 = max(0, x1 - (zoom_width - (y2 - y1)) // 2)

        # 确保裁剪区域有效
        if x2 <= x1 or y2 <= y1:
            print("警告: 无效的裁剪区域，使用原始图像")
            return frame

        try:
            # 裁剪并调整大小
            cropped = frame[y1:y2, x1:x2]

            # 检查裁剪区域是否为空
            if cropped.size == 0:
                print("警告: 裁剪区域为空，使用原始图像")
                return frame

            return cv2.resize(cropped, (w, h), interpolation=cv2.INTER_LINEAR)
        except Exception as e:
            print(f"缩放图像时出错: {str(e)}")
            return frame  # 出错时返回原始图像

    def set_zoom(self, factor: float, center: Optional[Tuple[float, float]] = None) -> bool:
        """
        设置缩放参数

        参数:
            factor: 缩放因子，大于1表示放大，小于1表示缩小
            center: 缩放中心点(x, y)，范围为0-1，默认为图像中心

        返回:
            bool: 是否成功设置
        """
        with self._lock:
            try:
                if factor <= 0:
                    print("错误: 缩放因子必须大于0")
                    return False

                # 限制缩放因子范围，防止过度缩放
                factor = max(0.1, min(10.0, factor))
                self._zoom_factor = factor

                if center is not None:
                    cx, cy = center
                    if not (0 <= cx <= 1 and 0 <= cy <= 1):
                        print("错误: 中心点坐标必须在0到1之间")
                        return False
                    self._zoom_center = center

                return True
            except Exception as e:
                print(f"设置缩放参数时出错: {str(e)}")
                return False

    def get_zoom_info(self) -> Tuple[float, Tuple[float, float]]:
        """
        获取当前缩放信息

        返回:
            Tuple[float, Tuple[float, float]]: (缩放因子, 缩放中心点)
        """
        with self._lock:
            return self._zoom_factor, self._zoom_center

    def zoom_in(self, step: float = 0.2, center: Optional[Tuple[float, float]] = None) -> bool:
        """
        放大图像

        参数:
            step: 放大步长，默认0.2
            center: 缩放中心点(x, y)，范围为0-1，默认保持当前中心

        返回:
            bool: 是否成功放大
        """
        current_factor = self._zoom_factor
        new_factor = current_factor + step
        return self.set_zoom(new_factor, center)

    def zoom_out(self, step: float = 0.2, center: Optional[Tuple[float, float]] = None) -> bool:
        """
        缩小图像

        参数:
            step: 缩小步长，默认0.2
            center: 缩放中心点(x, y)，范围为0-1，默认保持当前中心

        返回:
            bool: 是否成功缩小
        """
        current_factor = self._zoom_factor
        new_factor = max(0.1, current_factor - step)  # 确保不会缩放到过小
        return self.set_zoom(new_factor, center)

    def reset_zoom(self) -> bool:
        """
        重置缩放到原始大小

        返回:
            bool: 是否成功重置
        """
        return self.set_zoom(1.0, (0.5, 0.5))

    def zoom_to_factor(self, factor: float, center: Optional[Tuple[float, float]] = None) -> bool:
        """
        直接设置缩放因子

        参数:
            factor: 目标缩放因子
            center: 缩放中心点(x, y)，范围为0-1，默认保持当前中心

        返回:
            bool: 是否成功设置
        """
        return self.set_zoom(factor, center)

    def get_zoom_factor(self) -> float:
        """
        获取当前缩放因子

        返回:
            float: 当前缩放因子
        """
        with self._lock:
            return self._zoom_factor

    def set_zoom_center(self, center: Tuple[float, float]) -> bool:
        """
        设置缩放中心点

        参数:
            center: 缩放中心点(x, y)，范围为0-1

        返回:
            bool: 是否成功设置
        """
        return self.set_zoom(self._zoom_factor, center)

    def get_last_error(self) -> Optional[str]:
        """
        获取最后一次错误信息

        返回:
            Optional[str]: 错误信息，如果没有错误则为None
        """
        with self._lock:
            return self._last_error

    def get_camera_stats(self) -> Dict[str, Any]:
        """
        获取摄像头统计信息

        返回:
            Dict[str, Any]: 统计信息字典
        """
        with self._lock:
            return {
                "is_opened": self._is_opened,
                "camera_id": self._camera_id,
                "resolution": (self._width, self._height),
                "frame_count": self._frame_count,
                "last_frame_time": self._last_frame_time,
                "recovery_count": self._recovery_count,
                "last_error": self._last_error
            }

    def set_resolution(self, width: int, height: int) -> bool:
        """
        设置摄像头分辨率

        参数:
            width: 宽度
            height: 高度

        返回:
            bool: 是否成功设置
        """
        with self._lock:
            try:
                if width <= 0 or height <= 0:
                    print("错误: 宽度和高度必须大于0")
                    return False

                self._width = width
                self._height = height

                # 如果摄像头已打开，立即应用新分辨率
                if self.is_opened() and self._cap is not None:
                    self._cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
                    self._cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)

                    # 验证设置是否生效
                    actual_width = int(self._cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                    actual_height = int(self._cap.get(
                        cv2.CAP_PROP_FRAME_HEIGHT))

                    if abs(actual_width - width) > 10 or abs(actual_height - height) > 10:
                        print(
                            f"警告: 请求的分辨率 {width}x{height} 与实际设置的分辨率 {actual_width}x{actual_height} 不匹配")

                return True
            except Exception as e:
                self._last_error = f"设置分辨率时出错: {str(e)}"
                print(self._last_error)
                return False

    def get_resolution(self) -> Tuple[int, int]:
        """
        获取当前分辨率

        返回:
            Tuple[int, int]: (宽度, 高度)
        """
        with self._lock:
            if not self.is_opened():
                return (self._width, self._height)

            try:
                if self._cap is None:
                    return (self._width, self._height)

                width = int(self._cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                height = int(self._cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                return (width, height)
            except Exception as e:
                print(f"获取分辨率时出错: {str(e)}")
                return (self._width, self._height)

    def get_available_cameras(self) -> List[int]:
        """
        获取系统上可用的摄像头列表

        返回:
            List[int]: 可用摄像头ID列表
        """
        available_cameras = []

        # 在大多数系统上，摄像头ID从0开始，我们尝试前5个ID
        for i in range(5):
            try:
                cap = cv2.VideoCapture(i)
                if cap.isOpened():
                    available_cameras.append(i)
                cap.release()
            except:
                pass

        return available_cameras

    def reset(self) -> bool:
        """
        重置摄像头，关闭并重新打开

        返回:
            bool: 是否成功重置
        """
        with self._lock:
            self.close()
            return self.open()

    def __del__(self):
        """析构函数，确保资源被释放"""
        try:
            self.close()
        except:
            pass
